package handledb;

import java.sql.SQLException;
import java.util.*;
import java.util.logging.Logger;

public class DbPool {
    private static final Logger log = Logger.getLogger(DbPool.class.getSimpleName());
    PoolConfig config;
    private volatile Vector<Pipeline> cons;
    private volatile Map<Long, Pipeline> localPipeline = new HashMap<>();
    private volatile Map<Long, Pipeline> transactions = new HashMap<>();
    private static Map<String, ClassInfo> classInfos = new HashMap<>();

    private long time;
    private long cleanTime;
    private int pipelineSize;

    public DbPool(PoolConfig conf) {
        this.config = conf;
        TransactionManage.putDbPool(this);
        init();
    }

    public void closeConnections(){
        int f = 0;
        for (int i = 0; i < pipelineSize; i++) {
            Pipeline pipeline = cons.get(i);
            try {
                pipeline.hasFree = false;
                pipeline.rollback();
                pipeline.close();
            } catch (SQLException e) {
                f += 1;
                e.printStackTrace();
            }
        }
        System.out.println("dbpool closed num: " + (pipelineSize - f) + " failure num: " + f);
    }

    private void init() {
        try {
            Class.forName(config.driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        log.info("data pool init======================");
        log.info("url: " + config.url);
        log.info("user: " + config.user);
        log.info("pool activation max size: " + config.maxsize);
        log.info("pool activation min size: " + config.minsize);
        log.info("clearing passage interval: " + config.inteval+"ms");
        log.info("timeout resources will be forcibly released: " + config.waitTime+"ms");
        log.info("distributed transaction timeout: " + config.transactionTimeout+"ms");
        log.info("gets the number of failed connection attempts: " + config.attempts);
        cons = new Vector<>(config.minsize, config.expansionSize);
        time = System.currentTimeMillis();
        cleanTime = time;
        Pipeline pipeline = getPipeline();
        pipeline.testConnection();
        closeCurrentPipeline();
    }

    public void cleanTask(long t){
        time = t;
        if (time - cleanTime > config.inteval) {
            cleanTime = time;
            clean();
        }
        cleanTranscations();
    }

    // 获取连接
    public Pipeline getPipeline() {
        time = System.currentTimeMillis();
        Thread thread = Thread.currentThread();
        Pipeline pipeline = localPipeline.get(thread.getId());
        if (pipeline == null) {
            TransactionItem item = TransactionManage.getTransactionItem(thread.getId(),null);
            if(item!=null && item.transactionId!=0){
                log.info("add global transaction. id="+item.transactionId);
                pipeline = openTransactionById(item);
            }else{
                synchronized (localPipeline){
                    pipeline = getFreePipeline();
                    pipeline.testConnection();
                    pipeline.threadId = thread.getId();
                    localPipeline.put(thread.getId(), pipeline);
                }
            }
        }
        if(pipeline.threadId!=thread.getId()){
            System.err.println("当前连接异常 两个线程使用同一连接！");
        }
        if(pipeline.hasFree) {
            System.err.println("当前连接异常 线程是空闲的！-----------");
        }
        pipeline.time = time;
        return pipeline;
    }

    // 获取空闲的连接
    private Pipeline getFreePipeline() {
        Pipeline pipeline = null;
        for(int m = 0; m < 100; m++){
            boolean hasNext = true;
            for (int i = 0; i < pipelineSize; i++) {
                pipeline = cons.get(i);
                if (pipeline.hasFree) {
                    pipeline.hasFree = false;
                    hasNext = false;
                    break;
                }
            }
            if (hasNext) {
                pipeline = null;
                if (!(pipelineSize < config.maxsize && expansion())) {
                    try {
                        localPipeline.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                break;
            }
        }
        if (pipeline == null) {
            throw new RuntimeException("Database connection pool full and occupied Current size of the database connection pool: " + pipelineSize);
        }
        return pipeline;
    }

    // 扩容
    private boolean expansion() {
        if (pipelineSize < config.maxsize) {
            for (int i = 0; i < config.expansionSize; i++) {
                cons.add(new Pipeline(config, this));
            }
            pipelineSize = cons.size();
            return true;
        }
        return false;
    }

    // 清除长时间未使用的连接
    private void clean() {
        Pipeline pipeline = null;
        synchronized (localPipeline) {
            for (int i = 0; i < cons.size(); i++) {
                pipeline = cons.get(i);
                if (cleanTime - pipeline.time > config.waitTime && pipeline.transactionId == 0) {
                    try {
                        pipeline.hasFree = false;
                        pipeline.rollback();
                        pipeline.close();
                        pipelineSize--;
                        cons.remove(i);
                        i--;
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            localPipeline.values().removeIf(temp -> temp==null || temp.hasFree);
            localPipeline.notifyAll();
        }
        System.out.println("dbpool clean pipelineSize:" + pipelineSize + " localPipeline: " + localPipeline.size() + " transactions: " + transactions.size());
    }

    // 自动结束长时间未使用的事务
    private void cleanTranscations() {
        synchronized (localPipeline) {
            transactions.values().removeIf((p) -> {
                if (time - p.time > p.transactionTime) {
                    log.warning("distributed transaction time out, transaction is rollback. id: "+p.transactionId);
                    p.transactionTime = 0;
                    p.transactionId = 0l;
                    p.clearFields();
                    try {
                        p.rollback();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                    p.hasFree = true;
                    return true;
                }
                return false;
            });
        }
        System.out.println("dbpool cleanTranscations pipelineSize:" + pipelineSize + " == cons.size():" + cons.size() + " localPipeline: " + localPipeline.size() + " transactions: " + transactions.size());
    }

    public ClassInfo getClassInfo(Class c) {
        ClassInfo info = classInfos.get(c.getName());
        if (info == null) {
            synchronized (classInfos){
                info = classInfos.get(c.getName());
                if(info == null) {
                    info = new ClassInfo(c);
                    classInfos.put(c.getName(), info);
                }
            }
        }
        return info;
    }

    public long closeCurrentPipeline() {
        long id = Thread.currentThread().getId();
        Pipeline p = localPipeline.get(id);
        if (p != null) {
            p.clearFields();
            synchronized (localPipeline){
                localPipeline.put(id,null);
                if (p.transactionId == 0) {
                    p.hasFree = true;
                    p.noUpdate = true;
                }
                localPipeline.notify();
            }
            return p.transactionId;
        }
        return 0l;
    }

    public void commitAndCloseCurrentPipeline() {
        long id = Thread.currentThread().getId();
        Pipeline p = localPipeline.get(id);
        if (p != null) {
            p.clearFields();
            synchronized (localPipeline){
                localPipeline.put(id,null);
                if (p.transactionId == 0) {
                    try {
                        p.commit();
                    } catch (SQLException e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    } finally {
                        p.hasFree = true;
                    }
                }
                localPipeline.notify();
            }
        }
    }

    public void rollbackAndCloseCurrentPipeline() {
        long id = Thread.currentThread().getId();
        Pipeline p = localPipeline.get(id);
        if (p != null) {
            p.clearFields();
            synchronized (localPipeline){
                localPipeline.put(id,null);
                if (p.transactionId == 0) {
                    try {
                        p.rollback();
                    } catch (SQLException e) {
                        e.printStackTrace();
                        throw new RuntimeException(e);
                    } finally {
                        p.hasFree = true;
                    }
                }
                localPipeline.notify();
            }
        }
    }

    public long createTransaction(long id, long failureTime, boolean distributedTransaction){
        synchronized (transactions) {
            Pipeline pipeline = getFreePipeline();
            pipeline.transactionTime = failureTime;
            pipeline.time = System.currentTimeMillis();
            pipeline.transactionId = id;
            pipeline.distributedTransaction = distributedTransaction;
            transactions.put(id, pipeline);
            return id;
        }
    }

    public Pipeline openTransactionById(TransactionItem item) {
        if(transactions.get(item.transactionId)==null){
            createTransaction(item.transactionId,item.effectiveTime, true);
        }
        return useTransaction(item.transactionId);
    }

    public long openTransaction(long failureTime) {
        return createTransaction(System.currentTimeMillis(),failureTime, false);
    }

    public long openTransaction() {
        return createTransaction(System.currentTimeMillis(),config.transactionTimeout, false);
    }

    public long getValidTime(Long id) {
        Pipeline pipeline = transactions.get(id);
        if (pipeline == null) {
            return 0;
        } else {
            time = System.currentTimeMillis();
            return pipeline.transactionTime - (time - pipeline.time);
        }
    }

    public Pipeline useTransaction(Long id) {
        if (id == null || id == 0) {
            throw new RuntimeException("transaction id not null or zero!");
        }
        Pipeline pipeline = transactions.get(id);
        if (pipeline == null) {
            throw new RuntimeException("The transaction does not exist or is closed");
        } else if (pipeline.testConnection()) {
            Thread thread = Thread.currentThread();
            synchronized (localPipeline) {
                Pipeline oldPipeline = localPipeline.get(thread.getId());
                if(oldPipeline==null){
                    localPipeline.put(thread.getId(), pipeline);
                    pipeline.threadId = thread.getId();
                }else if(!id.equals(oldPipeline.transactionId)){
                    localPipeline.put(thread.getId(), pipeline);
                    try {
                        oldPipeline.rollback();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                    oldPipeline.hasFree = true;
                }
            }
        } else {
            transactions.remove(id);
            pipeline.transactionId = 0l;
            pipeline.threadId = 0l;
            pipeline.hasFree = true;
            throw new RuntimeException("The transaction is not available and will be closed");
        }
        return pipeline;
    }
    public void closeTransaction() {
        Thread thread = Thread.currentThread();
        Pipeline pipeline = localPipeline.get(thread.getId());
        if (pipeline == null) {
            throw new RuntimeException("The transaction does not exist or is closed");
        } else {
            transactions.remove(pipeline.transactionId);
            pipeline.distributedTransaction = false;
            pipeline.transactionTime = 0l;
            pipeline.transactionId = 0l;
        }
    }
    public void closeTransaction(Long id) {
        closeTransaction(id, null);
    }
    public void closeTransaction(Long id, String status){
        if (id == null || id == 0) {
            throw new RuntimeException("transaction id not null or zero!");
        }
        Pipeline pipeline = transactions.get(id);
        if (pipeline == null) {
            throw new RuntimeException("The transaction does not exist or is closed");
        } else {
            transactions.remove(id);
            pipeline.distributedTransaction = false;
            pipeline.transactionTime = 0l;
            pipeline.transactionId = 0l;
            synchronized (localPipeline){
                try {
                    if(TransactionManage.COMMIT.equals(status)){
                        pipeline.commit();
                    }else{
                        pipeline.rollback();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                } finally {
                    if(localPipeline.get(pipeline.threadId)==null){
                        pipeline.clearFields();
                        pipeline.threadId=0l;
                        pipeline.hasFree = true;
                    }
                    localPipeline.notify();
                }
            }
        }
    }
}
